# --coding:utf-8--

# Copyright 2021 xpstem.com
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import socket
import re
import time
from threading import Thread
from lelink.connection import *


# 接受数据，处理拆包、粘包。
class _Worker:
    def __init__(self, conn):
        self._conn = conn
        self._buffer = bytearray()

    def receive(self, data: bytearray) -> None:
        # parse data
        self._buffer.extend(data)
        tmp = self._buffer.copy()
        buf_len = len(tmp)
        if buf_len < 2:
            return  # wait more data.

        b0 = tmp.pop(0)
        b1 = tmp.pop(0)
        pkg_len = b0 + (b1 << 8) + 2
        if buf_len < pkg_len:
            return  # wait more data.

        b2 = tmp.pop(0)
        b3 = tmp.pop(0)
        counter = b2 + (b3 << 8)
        package = bytearray()
        while pkg_len > 0:
            package.append(self._buffer.pop(0))
            pkg_len = pkg_len - 1
        # remove pkg_len
        package.pop(0)
        package.pop(0)
        self._conn.response_data(counter, package)


# 接收服务端数据的工作线程
class _WorkThread(Thread):
    __slots__ = ("_running")

    def __init__(self, conn, sock: socket, worker: _Worker):
        Thread.__init__(self)
        self._conn = conn
        self._socket = sock
        self._worker = worker
        self._running = True

    def run(self) -> None:
        while self._running:
            try:
                data = self._socket.recv(10)
                if data is not None:
                    self._worker.receive(data)
            except BlockingIOError as ex:
                if ex.errno != socket.EAGAIN:
                    self._conn.socket_error(ex)
                    break
            except Exception as ex:
                self._conn.worker_error(ex)
                break

    def stop(self) -> None:
        self._running = False


class WifiConnection(BaseConnection):
    __slots__ = ("_server_ip",
                 "_serial_number",
                 "_server_port",
                 "_server_name",
                 "_protocol",
                 "_client_socket",
                 "_work_thread",
                 "_worker")

    def __init__(self):
        BaseConnection.__init__(self)

    def connect(self) -> None:
        self._find_server()
        self._connect_server()

    def _find_server(self) -> None:
        client_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        client_udp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        client_udp.bind(("", 3015))

        while True:
            try:
                package, addr = client_udp.recvfrom(67)
                msg = package.decode("utf-8")
                print('{}'.format(msg))

                matcher = re.match("Serial-Number:\\s(\\w*)\\s\\n" +
                                   "Port:\\s(\\d{4,4})\\s\\n" +
                                   "Name:\\s(\\w+)\\s\\n" +
                                   "Protocol:\\s(\\w+)\\s\\n", msg)
                if matcher is None:
                    return

                self._server_ip = addr[0]
                self._serial_number = matcher.group(1)
                self._server_port = int(matcher.group(2))
                self._server_name = matcher.group(3)
                self._protocol = matcher.group(4)

                client_udp.sendto(' '.encode('utf-8'), (addr[0], self._server_port))
                client_udp.close()
                break

            except Exception as ex:
                print(ex)

    def _connect_server(self) -> None:

        address = (self._server_ip, self._server_port)
        print("bind address: {}".format(address))
        self._client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._client_socket.connect(address)
        if self._client_socket is None:
            print("connect failure.")
            return

        data = "GET /target?sn={} VMTP1.0 Protocol: {}\n".format(self._serial_number, self._protocol).encode("utf-8")
        self._client_socket.send(data)
        package = self._client_socket.recv(16)
        msg = package.decode("utf-8")
        if not msg.startswith("Accept:EV340"):
            print("unlock failure, {}".format(msg))
            self.disconnect()
            return

        print("unlock success.")
        self._client_socket.setblocking(False)  # 非阻塞

        self._worker = _Worker(self)
        self._work_thread = _WorkThread(self, self._client_socket, self._worker)
        self._work_thread.start()

    def _do_send(self, data: bytearray) -> None:
        self._client_socket.send(data)

    def disconnect(self) -> None:
        if self._work_thread is not None:
            self._work_thread.stop()

        time.sleep(0.2)

        if self._client_socket is not None:
            self._client_socket.close()

    def socket_error(self, ex: BlockingIOError):
        self.disconnect()
        print(ex)

    def worker_error(self, ex: Exception):
        self.disconnect()
        print(ex)
